/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git.http;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.zip.CRC32;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

import org.zhiqim.git.GitZiper;
import org.zhiqim.git.constants.GitStreamConstants;

import org.zhiqim.kernel.util.Asserts;

/**
 * 接受输入流，包括处理校验码、保存到文件，和缓冲8K，以及每个对象进行CRC32
 * 1、校验码，所有读取的数据都加入计算（满缓冲或到最后）
 * 2、保存到临时文件，所有读取的数据都写入文件（满缓冲或到最后）
 * 3、设置缓冲为8K，每次读取8K或缓冲剩余
 * 4、每对象操作前重设CRC32，读完对象获取值
 *
 * @version v1.0.0 @author zouzhigang 2017-5-15 新建与整理
 */
public class ReceiveFileStream extends InputStream implements GitStreamConstants
{
    private final RandomAccessFile file;
    private final CRC32 crc;
    
    public final byte[] buf;
    public int offset;
    public int avail;
    
    public ReceiveFileStream(RandomAccessFile file)
    {
        this.file = file;
        this.crc = new CRC32();
        
        this.buf = new byte[BUFFER_SIZE];
    }
    
    public int read() throws IOException
    {
        if (avail == 0)
            fill(1);
        
        avail--;
        final int b = buf[offset++] & 0xFF;
        crc.update(b);
        return b;
    }
    
    public void seek(long pos) throws IOException
    {
        file.seek(pos);
        offset = 0;
        avail = 0;
    }
    
    private int fill(int need) throws IOException
    {
        while (avail < need)
        {
            int next = offset + avail;
            int free = buf.length - next;
            if (free + avail < need)
            {
                sync();
                next = avail;
                free = buf.length - next;
            }
            next = file.read(buf, next, free);
            if (next <= 0)
                throw new EOFException("包文件被截断");
            
            avail += next;
        }
        
        return offset;
    }
    
    private void sync() throws IOException
    {
        if (avail > 0)
            System.arraycopy(buf, offset, buf, 0, avail);
        offset = 0;
    }
    
    private void use(int cnt)
    {
        offset += cnt;
        avail -= cnt;
    }
    
    /******************************************************************************/
    // 业务处理
    /******************************************************************************/
    
    public void skip(int len) throws IOException
    {
        int off = fill(len);
        updateCRC32(off, len);
        use(len);
    }
    
    public void skipOfs() throws IOException
    {
        int c = read() & 0xFF;
        while ((c & 0x80) != 0)
        {//首位=1表示后续字节还是ofs
            c = read() & 0xFF;
        }
    }
    
    public byte[] inflate(long len) throws IOException
    {
        final byte[] dst = new byte[(int)len];
        final Inflater inf = GitZiper.inflater();
        
        try
        {
            int n = 0;
            int p = -1;
            while (!inf.finished())
            {
                if (inf.needsInput())
                {
                    if (p >= 0)
                    {
                        updateCRC32(p, avail);
                        use(avail);
                    }
                    
                    p = fill(1);
                    inf.setInput(buf, p, avail);
                }
                n += inf.inflate(dst, n, dst.length - n);
            }
            
            if (n != len)
                throw new DataFormatException("错误的解压长度");
            
            n = avail - inf.getRemaining();
            if (n > 0)
            {
                updateCRC32(p, n);
                use(n);
            }
            return dst;
        }
        catch (DataFormatException dfe)
        {
            throw Asserts.exception(dfe);
        }
        finally
        {
            GitZiper.close(inf);
        }
    }
    
    /******************************************************************************/
    // CRC32初始化&更新&获取
    /******************************************************************************/
    
    public void resetCRC32()
    {
        crc.reset();
    }
    
    public void updateCRC32(int off, int len)
    {
        crc.update(buf, off, len);
    }
    
    public int getCRC32()
    {
        return (int)crc.getValue();
    }
}
